#include "filemmap.h"
#include "log.hpp"

#define PAGEVALUE 4096

typedef long long ll;

xxhhdzl::log l(1);

fileMmap::fileMmap(const std::string path, mode_t mode = 0666)
{
    _fd = open(path.c_str(), O_CREAT | O_RDWR, mode);
    if (_fd == -1)
    {
        l(Error, __LINE__, "filemmap: file open lose");
        exit(-1);
    }

    //先获取文件属性
    fstat(_fd, &_attribute);

    //获取初始时文件内容的大小
    _size = static_cast<ll>(_attribute.st_size);

    //预分配文件空间
    ll fileSize = (ceil(_size / PAGEVALUE) + 3) * PAGEVALUE;
    ll check = ftruncate(_fd, static_cast<off_t>(fileSize));
    if (check == -1)
    {
        l(Error, __LINE__, "fileMmap->ftruncate: %s", strerror(errno));
        exit(-1);
    }
    _capacity = fileSize;

    //开始映射
    char* temp = static_cast<char*>(mmap(nullptr, static_cast<size_t>(fileSize), PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0));
    if (temp == MAP_FAILED)
    {
        l(Error, __LINE__, "fileMmap->mmap:%s", strerror(errno));
        exit(-1);
    }
    _file.push_back({temp, static_cast<off_t>(fileSize)});
    _offset.push_back(0);
}

ll fileMmap::size()
{
    return _size;
}

bool fileMmap::write(const std::string& writeContent)
{
    ll contentSize = static_cast<ll>(writeContent.size()) + _size;
    //判断是否需要扩容
    if (contentSize <= _capacity)
    {
        //查找块
        ll blockN = dichotomy(contentSize);

        //判断当前块存储空间是否足够
        if (_file[blockN].second > (contentSize - _offset[blockN]))
        {
            memcpy(_file[blockN].first + _size, writeContent.c_str(), writeContent.size());
        }
        else
        {
            //先写入一部分
            ll part = contentSize - _offset[blockN] - _file[blockN].second;
            memcpy(_file[blockN].first + _size, writeContent.c_str(), part);
            //更换块后，再写入一部分
            blockN++;
            memcpy(_file[blockN].first, writeContent.c_str() + part, writeContent.size() - part);
        }  
    }
    else
    {
        //先写入一部分
        ll part = _capacity - _size;
        memcpy(_file.back().first + _size, writeContent.c_str(), part);

        //扩容
        if (!expansion(static_cast<ll>(writeContent.size())))
        {
            l(Error, __LINE__, "write: expansion error");
            return false;
        }

        //写入另一部分
        memcpy(_file.back().first, writeContent.c_str() + part, writeContent.size() - part);
    }

    _size += static_cast<ll>(writeContent.size());

    return true;
}

bool fileMmap::expansion(ll size)
{
    //开始扩容，并加入数组
    ll fileSize = ceil(size / PAGEVALUE) * PAGEVALUE;
    ll check = ftruncate(_fd, fileSize + _capacity);
    if (check == -1)
    {
        l(Error, __LINE__, "fileMmap->ftruncate: %s", strerror(errno));
        return false;
    }

    //添加偏移量
    _offset.push_back(_capacity);

    char* temp = static_cast<char*>(mmap(nullptr, static_cast<size_t>(fileSize), PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _capacity));
    if (temp == MAP_FAILED)
    {
        l(Error, __LINE__, "expansion->mmap:%s", strerror(errno));
        return false;
    }
    _file.push_back({temp, static_cast<off_t>(fileSize)});

    //更改容量
    _capacity += fileSize;

    return true;
}

bool fileMmap::clear()
{
    _size = 0;
    return true;
}

ll fileMmap::dichotomy(ll target)
{
    ll left =0, right = _offset.size() - 1;
    while (left < right)
    {
        ll mid = (right - left) / 2 + left;
        if (_offset[mid] > target)
        {
            right = mid - 1;
        }
        else if (_offset[mid] <= target)
        {
            if (target <= _file[mid].second + _offset[mid])
            {
                return mid;
            }
            else
            {
                left = mid + 1;
            }
        }
    }
    return left;
}

std::string fileMmap::read(size_t start, size_t end)
{
    if (start > _size || start >= end)
    {
        l(Error, __LINE__, "fileMmap->read: start exceet content size");
        return std::string();
    }

    if (end == 0)
    {
        return std::string();
    }

    if (end == END)
    {
        end = _size;
    }


    //得到开始位置所在的映射
    ll startBlock = dichotomy(static_cast<ll>(start));
    ll endBlock = dichotomy(static_cast<ll>(end - 1)); 

    std::string ans;
    ll block_start, block_end, read_len;

    //读取起始块的部分数据
    block_start = _offset[startBlock];
    block_end = block_start + _file[startBlock].second;
    ll read_start = start - block_start;  // 块内起始偏移
    read_len = std::min(block_end, static_cast<ll>(end)) - start;  // 限制不超过 end
    ans.append(_file[startBlock].first + read_start, static_cast<size_t>(read_len));

    //读取中间完整块（若有）
    for (ll i = startBlock + 1; i < endBlock; ++i) 
    {
        ans.append(_file[i].first, static_cast<size_t>(_file[i].second));
    }

    //读取结束块的部分数据（若起始块 != 结束块）
    if (startBlock != endBlock) 
    {
        block_start = _offset[endBlock];
        read_start = 0;
        read_len = end - block_start;  // 读取到 end（不含）
        ans.append(_file[endBlock].first + read_start, static_cast<size_t>(read_len));
    }

    return ans;
}

fileMmap::~fileMmap()
{
    //缩短文件大小
    ll check = ftruncate(_fd, static_cast<off_t>(_size));
    if (check == -1)
    {
        l(Error, __LINE__, "fileMmap->ftruncate: %s", strerror(errno));
        exit(-1);
    }

    //解除映射
    for (auto f : _file)
    {
        munmap(f.first, f.second);
    }

    //关闭文件
    close(_fd);
}
